package cn.tanghom.framework.system.shiro;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;

import cn.tanghom.framework.support.tree.TreeObject;
import cn.tanghom.framework.system.dao.UserDao;
import cn.tanghom.framework.system.entity.Resources;
import cn.tanghom.framework.system.entity.Role;
import cn.tanghom.framework.system.entity.User;
import cn.tanghom.framework.system.service.ResourcesService;
import cn.tanghom.framework.utils.CommonKey;

/**
 * 自定义CasRealm,进行数据源配置，单点登录时使用
 * 
 * @author tanghom <tanghom@qq.com> 2015-11-18
 */
public class MyCasRealm extends CasRealm {
    @Autowired
    private UserDao userDao;
    @Autowired
    private ResourcesService resourcesService;

    /**
     * 只有需要验证权限时才会调用, 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.在配有缓存的情况下，只加载一次. 2015年10月27日
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("----------CasRealm验证权限------------------");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 获取登录时输入的用户名
        String loginName = (String) principals.getPrimaryPrincipal();
        // 到数据库查是否有此对象
        User user = userDao.getUserByloginName(loginName);
        if (user != null) {
            if (user.getId() == 1L) {// 超级管理员 始终拥有所有权限
                info.addStringPermission("*");
                return info;
            }
            List<Role> roles = user.getRoles();
            HashSet hs = new LinkedHashSet();
            for (Role role : roles) {
                Long roleId = role.getId();
                hs.add(roleId);
                // 角色对应相应的权限
                List<Resources> res = role.getResources();
                for (Resources resources : res) {
                    info.addStringPermission(resources.getKey().toString());
                }
            }
            info.setRoles(hs);
        }
        return info;
    }

    /**
     * 认证回调函数,登录时调用
     * 首先根据传入的用户名获取User信息；然后如果user为空，那么抛出没找到帐号异常UnknownAccountException；
     * 如果user找到但锁定了抛出锁定异常LockedAccountException；最后生成AuthenticationInfo信息，
     * 交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配，
     * 如果不匹配将抛出密码错误异常IncorrectCredentialsException；
     * 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException；
     * 在组装SimpleAuthenticationInfo信息时， 需要传入：身份信息（用户名）、凭据（密文密码）、盐（username+salt），
     * CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CasToken casToken = (CasToken) token;
        if (token == null)
            return null;
        String ticket = (String) casToken.getCredentials();
        if (ticket.equals(""))
            return null;
        try {
            TicketValidator ticketValidator = ensureTicketValidator();
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String loginName = casPrincipal.getName();
            User user = userDao.getUserByloginName(loginName);
            Map attributes = casPrincipal.getAttributes();
            List principals = CollectionUtils.asList(new Object[] { loginName, attributes });
            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
            // 当验证都通过后，把用户信息放在session里
            Session session = SecurityUtils.getSubject().getSession();
            session.setAttribute(CommonKey.USER_SESSION, user);
            getMenus(user.getId());
            return new SimpleAuthenticationInfo(principalCollection, ticket);
        } catch (TicketValidationException e) {
            throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(), e);
        }

    }

    public void getMenus(Long id) {
        List<TreeObject> list = new ArrayList<TreeObject>();
        List<TreeObject> ns = resourcesService.queryResourcesTreeByUserId(id);
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute(CommonKey.MENU_SESSION, ns);
    }

    /**
     * 更新用户授权信息缓存.
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 更新用户信息缓存.
     */
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    /**
     * 清空所有缓存
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 清除用户授权信息缓存.
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 清除用户信息缓存.
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 清空所有认证缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }

}
